#include "LeGraphicsScene.h"
#include "LeGraphicsStyle.h"
#include "LeGraphicsItem.h"
#include "LeGraphicsHandleItem.h"
#include "LeGraphicsItemLayer.h"

#include <QDebug>
#include <QElapsedTimer>
#include <QGraphicsDropShadowEffect>

LeGraphicsScene::LeGraphicsScene(QObject *parent):
    QGraphicsScene(parent),
    m_style(new LeGraphicsStyle(this)),
    m_backgroundLayer(new LeGraphicsItemLayer),
    m_foregroundLayer(new LeGraphicsItemLayer),
    m_currentLayer(nullptr),
    m_layerTransparencyEnabled(false),
    m_layerOpacity(0.5),
    m_toolTipEnabled(false),
    m_eventFilter(nullptr)
{
    addItem(m_backgroundLayer);
    addItem(m_foregroundLayer);

    updateSelection();
    connect(this, &LeGraphicsScene::selectionChanged,
            this, &LeGraphicsScene::updateSelection);
}

LeGraphicsItemLayer *LeGraphicsScene::backgroundLayer() const
{
    return m_backgroundLayer;
}

LeGraphicsItemLayer *LeGraphicsScene::foregroundLayer() const
{
    return m_foregroundLayer;
}

void LeGraphicsScene::clearLayers()
{
    m_backgroundLayer->clear();
    setLayers(QList<LeGraphicsItemLayer *>());
    m_foregroundLayer->clear();
    setSceneRect(QRectF());
}

void LeGraphicsScene::setLayers(const QList<LeGraphicsItemLayer *> &layers)
{
    for (auto layer: m_layerList)
    {
        removeItem(layer);
        delete layer;
    }

    m_layerList = layers;

    if (m_layerList.isEmpty())
        return;

    // Insert all layers
    for (auto layer: m_layerList)
    {
        emit aboutToAddLayer();
        layer->setOpacity(m_layerTransparencyEnabled ? m_layerOpacity : 1.0);
        layer->setEnabled(false);
        addItem(layer);
        emit layerAdded();
    }
    // Set top layer as current one, this will re-arrange the whole drawing order
    m_currentLayer = nullptr;
    setCurrentLayer(m_layerList.first());
}

void LeGraphicsScene::changeLayerOrdering(const QList<LeGraphicsItemLayer *> &order)
{
    if (order.toSet() != m_layerList.toSet())
    {
        qWarning() << "LeGraphicsScene::changeLayerOrdering: new and current layer ordering contain a different set of layers";
        return;
    }

    emit aboutToChangeLayerOrdering();
    m_layerList = order;
    updateLayerItemDrawingOrder();

    emit layerOrderingChanged();
}

QList<LeGraphicsItemLayer *> LeGraphicsScene::featureLayers() const
{
    return m_layerList;
}

LeGraphicsItemLayer *LeGraphicsScene::currentLayer() const
{
    return m_currentLayer;
}

// TODO: Add an enable profiling flag
void LeGraphicsScene::updateLayerItemDrawingOrder()
{
    QElapsedTimer timer;
    timer.start();

    m_currentLayer->stackBefore(m_foregroundLayer);
    auto topLayer = m_currentLayer;
    for (auto layer: m_layerList) // top-bottom
    {
        if (layer == m_currentLayer)
            continue;
        layer->stackBefore(topLayer); // bottom-top
        topLayer = layer;
    }

    qDebug() << QString("GraphicsScene: Drawing order updated in %1 ms").arg(timer.elapsed());

    invalidate();
}

void LeGraphicsScene::setCurrentLayer(LeGraphicsItemLayer *layer)
{
    if (m_currentLayer == layer)
        return;

    if (m_currentLayer != nullptr)
        m_currentLayer->setEnabled(false);

    m_currentLayer = layer;

    if (m_currentLayer != nullptr)
        m_currentLayer->setEnabled(true);

    updateLayerItemDrawingOrder();
    emit currentLayerChanged(m_currentLayer);
}

// Move out of scene?
void LeGraphicsScene::enableTransparentLayers(bool enabled)
{
    if (m_layerTransparencyEnabled == enabled)
        return;

    m_layerTransparencyEnabled = enabled;

    qreal opacity = enabled ? m_layerOpacity : 1.0;
    for (auto layer: m_layerList)
        layer->setOpacity(opacity);
}

// TODO: Move out of scene
void LeGraphicsScene::enableDropShadowEffect(bool enabled)
{
    if (enabled)
    {
        auto effect = new QGraphicsDropShadowEffect;
        effect->setOffset(25);
        effect->setBlurRadius(25);
        effect->setColor(graphicsPalette().color(LeGraphicsPalette::Background).darker());
        m_backgroundLayer->setGraphicsEffect(effect);
    }
    else
        m_backgroundLayer->setGraphicsEffect(nullptr);
}

// TODO: Move out of scene
void LeGraphicsScene::setFeatureLayersOpacity(qreal opacity)
{
    if (qFuzzyCompare(m_layerOpacity, opacity))
        return;

    m_layerOpacity = opacity;

    if (!m_layerTransparencyEnabled)
        return;

    for (auto layer: m_layerList)
        layer->setOpacity(m_layerOpacity);
}

// FIXME: GraphicsPalette should be managed by GraphicsView, not scene
void LeGraphicsScene::setGraphicsPalette(const LeGraphicsPalette &graphicsPalette)
{
    if (m_palette == graphicsPalette)
        return;

    m_palette = graphicsPalette;

    setBackgroundBrush(m_palette.brush(LeGraphicsPalette::Background));
    invalidate();
}

LeGraphicsPalette LeGraphicsScene::graphicsPalette() const
{
    return m_palette;
}

LeGraphicsStyle *LeGraphicsScene::graphicsStyle() const
{
    return m_style;
}

void LeGraphicsScene::setEventFilter(LeGraphicsSceneEventFilter *filter)
{
    m_eventFilter = filter;
}

void LeGraphicsScene::enableToolTip(bool set)
{
    m_toolTipEnabled = set;
}

bool LeGraphicsScene::toolTipEnabled() const
{
    return m_toolTipEnabled;
}

void LeGraphicsScene::updateSelection()
{
    // Make item's handle visible only when one item is selected
    // Items hide their handle themselves as they are de-selected
    for (auto item: selectedItems())
    {
        auto i = static_cast<LeGraphicsItem*>(item);
        if (i != nullptr)
        {
            for (auto handle: i->handles())
                handle->show();
            return;
        }
    }
}

// TODO: just store "scalefactor"
qreal LeGraphicsScene::unitScaleFactor() const
{
    return m_unitScaleFactor;
}

void LeGraphicsScene::setUnitScaleFactor(qreal factor)
{
    m_unitScaleFactor = factor;
}

QString LeGraphicsScene::unitOfMeasure() const
{
    return m_unitOfMeasure;
}

void LeGraphicsScene::setUnitOfMeasure(const QString &unit)
{
    m_unitOfMeasure = unit;
}

void LeGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->mousePressEvent(event);
    if (!filtered)
        QGraphicsScene::mousePressEvent(event);
}

void LeGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->mouseMoveEvent(event);
    if (!filtered)
        QGraphicsScene::mouseMoveEvent(event);
}

void LeGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->mouseReleaseEvent(event);
    if (!filtered)
        QGraphicsScene::mouseReleaseEvent(event);
}

void LeGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->mouseDoubleClickEvent(event);
    if (!filtered)
        QGraphicsScene::mouseDoubleClickEvent(event);
}


void LeGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
    QGraphicsScene::drawBackground(painter, rect);
    if (m_eventFilter != nullptr)
        m_eventFilter->drawBackground(painter, rect);
}

void LeGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect)
{
    QGraphicsScene::drawForeground(painter, rect);
    if (m_eventFilter != nullptr)
        m_eventFilter->drawForeground(painter, rect);
}


#include <QDebug>
#include <QKeyEvent>
#define DEBUG() qDebug() << __PRETTY_FUNCTION__

void LeGraphicsScene::keyPressEvent(QKeyEvent *event)
{
    DEBUG() << event->key();

    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->keyPressEvent(event);
    if (!filtered)
        QGraphicsScene::keyPressEvent(event);
}

void LeGraphicsScene::keyReleaseEvent(QKeyEvent *event)
{
    DEBUG() << event->key();

    bool filtered = false;
    if (m_eventFilter != nullptr)
        filtered = m_eventFilter->keyReleaseEvent(event);
    if (!filtered)
        QGraphicsScene::keyReleaseEvent(event);
}
